<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8" />
    <meta name="author" content="Vasanth.V" />
    <meta name="theme-color" content="#FFFFFF" />
    <link rel="apple-touch-icon" sizes="180x180" href="https://img.hellogithub.com/favicon/apple-touch-icon.png">
    <link rel="android-chrome" sizes="192x192" href="https://img.hellogithub.com/favicon/android-chrome-192x192.png">
    <link rel="android-chrome" sizes="512x512" href="https://img.hellogithub.com/favicon/android-chrome-512x512.png">
    <link rel="icon" type="image/png" sizes="32x32" href="https://img.hellogithub.com/favicon/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="https://img.hellogithub.com/favicon/favicon-16x16.png">
    <link rel="icon" href="https://img.hellogithub.com/favicon/favicon.ico">
    
    <title>Looptap - 消磨时间的小游戏</title>
    <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>

    <style>
        html,
        body {
            overscroll-behavior: none;
        }

        body {
            font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;
            margin: 0;
            line-height: 1.5;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            -webkit-text-size-adjust: 100%;
            background: #fbf9f6;
        }

        a {
            color: inherit;
        }

        #canvas {
            display: none;
            height: 100vh;
            width: 100%;
            max-width: 640px;
            overflow: hidden;
            box-sizing: border-box;
            margin: auto;
            flex-direction: column;
            justify-content: center;
        }

        #canvas svg {
            width: 100%;
        }

        #about {
            text-align: center;
            font-size: 1.2rem;
            line-height: 1.75;
            padding: 0rem 2rem 1rem;
            color: #2c3d51;
        }

        #about a {
            font-weight: 500;
            font-style: italic;
        }

        #ball {
            transform: translateZ(0);
            animation: rot 2000ms infinite linear;
            animation-play-state: paused;
        }

        #ball.started {
            animation-play-state: running;
        }

        #play {
            cursor: pointer;
        }

        #finalscore,
        #best {
            font-size: 70%;
            fill: #34495e;
        }

        #best {
            font-style: italic;
            font-size: 50%;
        }

        #tip {
            font-weight: 300;
            font-size: 18%;
            font-style: italic;
            padding: 0.5rem 2rem 0rem;
        }

        #shareBtn {
            background: #1d9bf0;
            color: #fff;
            width: 150px;
            text-align: center;
            text-decoration: none;
            border-radius: 2rem;
            padding: 0.3rem 1.5rem;
            margin: 1rem auto 0px;
            font-size: 1.25rem;
        }

        #shareBtn.hide {
            visibility: hidden;
        }

        @keyframes rot {
            from {
                transform: rotate(0deg) translate(40%) rotate(0deg);
            }

            to {
                transform: rotate(360deg) translate(40%) rotate(-360deg);
            }
        }
    </style>

</head>

<body>
    <section id="canvas" v-bind:style="'display:flex;'">
        <svg id="looptap" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
            <rect id="bg" x="0" cy="0" height="100" width="100" fill="none" />
            <text x="50" y="50" dominant-baseline="middle" text-anchor="middle" class="score" id="score"
                v-if="state === 'started'" v-html="score"></text>
            <text x="50" y="32" text-anchor="middle" class="score" id="finalscore" v-if="state === 'stopped'"
                v-html="score"></text>
            <text x="50" y="70" text-anchor="middle" class="score" id="best" v-if="state === 'stopped'"
                v-html="'Best: '+best"></text>
            <g id="tip" v-if="state === 'init'">
                <text x="50" y="68" text-anchor="middle" class="tip">
                    点击 ▶️ 开始 / 点击任何地方停止
                </text>
                <text x="50" y="74" text-anchor="middle" class="tip">
                    让球停在有颜色的区域就行！
                </text>
            </g>
            <path id="arc" fill="none"
                v-bind:stroke="colors[Math.floor(score / 10)] || colors[Math.floor((score - 270) / 10)] || '#bdc3c7'"
                stroke-width="10" stroke-linejoin="round" stroke-linecap="round" v-bind:d="arcDValue" />
            <circle id="ball" cx="50" cy="50" r="4" fill="#2C3D51" v-bind:class="state"
                v-bind:style="'animation-duration: '+(2000 - taps * 40) + 'ms'" />
            <polygon id="play" points="45,45 55,50 45,55" fill="#2C3D51" stroke="#2C3D51" stroke-width="5"
                stroke-linejoin="round" stroke-linecap="round" v-if="state !== 'started'" v-on:click="startPlay" />
        </svg>

        <a id="shareBtn"
            v-bind:href="'https://service.weibo.com/share/share.php?url=https%3A%2F%2Fhellogithub.com%2Fonfile%2Fcode%2Fcc759276aefe4bad87ac259940042581&title=%E6%88%91%E7%9A%84%E5%88%86%E6%95%B0%EF%BC%9A'+score+'%EF%BC%8CLooptap%20-%20%E6%B6%88%E7%A3%A8%E6%97%B6%E9%97%B4%E7%9A%84%E5%B0%8F%E6%B8%B8%E6%88%8F%E3%80%82'"
            v-if="['stopped', 'started'].includes(state)" target="_blank"
            v-bind:class="state === 'started' ? 'hide' : ''">
            分享你的分数
        </a>
        <div id="about" v-if="state === 'init'">
            源码：
            <a href="https://github.com/vasanthv/looptap" target="_blank">Vasanth.V</a><br />
            灵感来自 iOS 游戏 Coogyloop
        </div>
    </section>
</body>
<script>
    const loopTapApp = new Vue({
        el: "#canvas",
        data: {
            arc: [180, 270],
            taps: 0,
            score: 0,
            best: window.localStorage.best || 0,
            state: "init",
            prevTapTime: 0,
            colors: [
                '#F26D22',
                '#F1B21D',
                '#ECE82F',
                '#AED136',
                '#7EC242',
                '#63BC46',
                '#65BD5D',
                '#64C29A',
                '#61C8D2',
                '#2DAAE1',
                '#456EB5',
                '#4451A3',
                '#6151A2'
            ]
        },
        computed: {
            arcDValue: function () {
                return this.describeArc(50, 50, 40, this.arc[0], this.arc[1]);
            },
        },
        methods: {
            polarToCartesian: function (centerX, centerY, radius, angleInDegrees) {
                const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;
                return {
                    x: centerX + radius * Math.cos(angleInRadians),
                    y: centerY + radius * Math.sin(angleInRadians),
                };
            },

            describeArc: function (x, y, radius, startAngle, endAngle) {
                const start = this.polarToCartesian(x, y, radius, endAngle);
                const end = this.polarToCartesian(x, y, radius, startAngle);
                const arcFlag = endAngle - startAngle <= 180 ? "0" : "1";
                const d = ["M", start.x, start.y, "A", radius, radius, 0, arcFlag, 0, end.x, end.y].join(" ");
                return d;
            },

            getAngle: function (cx, cy, ex, ey) {
                const dy = ey - cy;
                const dx = ex - cx;
                let theta = Math.atan2(dx, -dy);
                theta *= 180 / Math.PI;
                theta = theta < 0 ? theta + 360 : theta;
                return theta;
            },

            getBallAngle: function () {
                const bg = document.getElementById("bg").getBoundingClientRect();
                const bgCenter = { x: bg.left + bg.width / 2, y: bg.top + bg.height / 2 };
                const ball = document.getElementById("ball").getBoundingClientRect();
                const ballCenter = { x: ball.left + ball.width / 2, y: ball.top + ball.height / 2 };
                return this.getAngle(bgCenter.x, bgCenter.y, ballCenter.x, ballCenter.y);
            },

            setArc: function () {
                const random = (i, j) => Math.floor(Math.random() * (j - i)) + i;
                arc = [];
                arc.push(random(0, 300));
                arc.push(random(arc[0] + 10, arc[0] + 110));
                arc[1] = arc[1] > 360 ? 360 : arc[1];
                this.arc = arc;
            },

            startPlay: function () {
                this.state = "started";
                this.taps = 0;
                this.score = 0;
                this.prevTapTime = Date.now();
            },
            stopPlay: function () {
                if (this.state === "started") {
                    this.state = "stopped";
                    if (this.score > this.best) window.localStorage.best = this.best = this.score;
                }
            },

            tap: function (e) {
                e.preventDefault();
                e.stopPropagation();
                if (this.state === "started") {
                    const ballAngle = this.getBallAngle();
                    // adding a 6 for better accuracy as the arc stroke extends beyond the angle.
                    if (ballAngle + 6 > this.arc[0] && ballAngle - 6 < this.arc[1]) {
                        const currentTapTime = Date.now();
                        const tapInterval = currentTapTime - this.prevTapTime;
                        this.taps++;
                        this.score = this.score + (tapInterval < 500 ? 5 : tapInterval < 1000 ? 2 : 1);
                        this.prevTapTime = currentTapTime;
                        this.setArc();
                    } else this.stopPlay();
                }
            },
        },
    });

    if ("ontouchstart" in window) {
        window.addEventListener("touchstart", loopTapApp.tap);
    } else {
        window.addEventListener("mousedown", loopTapApp.tap);
        window.onkeypress = (e) => {
            if (e.keyCode == 32) {
                if (loopTapApp.state === "stopped") {
                    loopTapApp.startPlay();
                } else {
                    loopTapApp.tap(e);
                }
            }
        };
    }


</script>

</html>